#include <iostream>
#include <vector>
#include <string>
#include <cassert>
#include <cstdlib>
#include <ctime>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>

#define PROCESS_NUM 5
#define MakeSeed() srand((unsigned long)time(nullptr) ^ 0x173629 ^ rand() % 1549)
// ---------------------------任务---------------------------
typedef void(*taskFunc)();

void task1()
{
    std::cout << "task1 is running" << std::endl << std::endl;
    sleep(1);
}
void task2()
{
    std::cout << "task2 is running" << std::endl << std::endl;
    sleep(1);
}
void task3()
{
    std::cout << "task3 is running" << std::endl << std::endl;
    sleep(1);
}
void loadTasks(std::vector<taskFunc>& tasks)
{
    tasks.push_back(task1);
    tasks.push_back(task2);
    tasks.push_back(task3);
}
// ---------------------------任务---------------------------


struct subProEnd
{
    subProEnd(pid_t subId, int writeFd)
        : _subId(subId), _writeFd(writeFd)
    {
        char buffer[1024];
        snprintf(buffer, sizeof buffer, "process(%d)_pid(%d)_writeFd(%d)", num++, _subId, _writeFd);
        _subName = buffer;
    }

    static int num;
    std::string _subName;
    pid_t _subId;
    int _writeFd;
};
int subProEnd::num = 1;


int receiveTask(int readFd)
{
    // 从管道中读取父进程派发的任务序号

    int taskNum = 0;// return code
    ssize_t n = read(readFd, &taskNum, sizeof taskNum);
    if(n == sizeof(taskNum)) return taskNum;
    else if(n == -1) return -1;// read error
    else return -1;// n == 0 -> 读到文件结束
}

void createSubProc(std::vector<subProEnd> &procs, std::vector<taskFunc>& tasks)
{
    int taskNum = tasks.size();
    std::vector<int> writeFds;// 记录子进程继承得来的writeFd

    for (int i = 0; i < PROCESS_NUM; ++i)
    {
        // 关闭子进程因继承父进程数据结构（实际是PCB中的文件描述符表）而得来的writeFd
        for(int i = 0; i < writeFds.size(); ++i) close(writeFds[i]);

        int fds[2];
        int n = pipe(fds);
        assert(n == 0); // 管道必须创建成功
        (void)n;        // n只在这里被用到，因此，为了防止报警告，将其强转为二进制数据

        pid_t id = fork();
        if (id == 0)
        {
            // 子进程，只读数据，关闭写端
            close(fds[1]);

            // 子进程处理任务
            while(true)
            {
                // 子进程获取命令码，即要被处理的任务的序号
                int commandCode = receiveTask(fds[0]);
                // 调用相关处理方法处理任务
                if(commandCode >= 0 && commandCode < taskNum) tasks[commandCode]();
                else break;// 读取错误，或读到文件结尾
            }

            exit(0);
        }

        // 父进程，只写数据，关闭读端
        close(fds[0]);
        subProEnd subObj(id, fds[1]);
        procs.push_back(subObj);
    }
}

void sendTaskToSubproc(subProEnd& subProc, int taskIndex)
{
    std::cout << "send task " << taskIndex+1 << " to subProcess " << subProc._subName << std::endl;
    ssize_t n = write(subProc._writeFd, &taskIndex, sizeof taskIndex);
    assert(n == sizeof(int));
    (void)n;
}

void controlSubproc(std::vector<subProEnd>& procs, std::vector<taskFunc>& tasks, int num)
{
    // 负载均衡，任务派发给子进程的概率相等
    int taskNum = tasks.size();
    while(true)
    {
        // 2.1 选择一个子进程
        int subprocIndex = rand() % PROCESS_NUM;
        // 2.2 选择一个任务
        int taskIndex = rand() % taskNum;
        // 2.3 将任务发送给子进程，让子进程处理
        sendTaskToSubproc(procs[subprocIndex], taskIndex);

        --num;
        if(num == 0) break;

        sleep(1);
    }

    // 关闭父进程的管道的写端
    for(int i = 0; i < PROCESS_NUM; ++i)
    {
        close(procs[i]._writeFd);
    }
}

void waitSubproc(std::vector<subProEnd>& procs)
{
    for(int i = 0; i < PROCESS_NUM; ++i)
    {
        int ret = waitpid(procs[i]._subId, nullptr, 0);
        if(ret == procs[i]._subId)
        {
            printf("wait sub process [pid:%d] success.\n", procs[i]._subId);
        }
    }
}

int main()
{
    // 生成随机数种子
    MakeSeed();

    // 1.创建子进程，并建立和子进程之间的信道（即匿名管道）
    std::vector<taskFunc> tasks;
    loadTasks(tasks);

    std::vector<subProEnd> procs;
    createSubProc(procs, tasks);

    // 父进程
    // 2. 控制子进程
    int num = 4;
    controlSubproc(procs, tasks, num);
    
    // 3. 回收子进程
    waitSubproc(procs);

    return 0;
}